package com.palm.easy.service.impl;

import com.palm.easy.ManagerConfiguration;
import com.palm.easy.util.Util;
import com.palm.easy.detector.FileDetector;
import com.palm.easy.detector.ProcessDetector;
import com.palm.easy.domain.FileInfo;
import com.palm.easy.domain.FileData;
import com.palm.easy.domain.LogPart;
import com.palm.easy.domain.Service;
import com.palm.easy.log.LoggerBuilder;
import com.palm.easy.service.Server;
import com.palm.easy.util.StringUtil;
import com.sun.management.OperatingSystemMXBean;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.annotation.*;
import org.noear.solon.core.handle.MethodType;
import org.noear.solon.core.util.PrintUtil;
import org.noear.solon.extend.health.detector.*;
import org.slf4j.Logger;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
@Mapping(value = "/server", method = MethodType.WEBSOCKET)
@Remoting
public class LocalServer implements Server {
   //private ExecutorService executorService;
    //    @Inject
//    DB db;
    HashMap<String, Service> serviceMap;
    Map<String, Process> processMap = new ConcurrentHashMap<>();

    private String servicePath;

    @Init
    public void init() {
       // executorService = Executors.newCachedThreadPool();
        //serviceMap = db.createHashMap("services").makeOrGet();
        String basePath = ManagerConfiguration.localPath();
        servicePath = new File(basePath, "services/").getAbsolutePath();
        serviceMap = new HashMap<>();
        File servicesDir = new File(servicePath);
        if (!servicesDir.exists()) {
            servicesDir.mkdirs();
        }
        for (File sdir : servicesDir.listFiles()) {
            if (sdir.isDirectory()) {
                File s = new File(sdir, "service.json");
                if (s.exists()) {
                    Service service = ONode.deserialize(StringUtil.readFrom(s), Service.class);
                    if (service.isRunning() && service.getProcessId() != null) {
                        boolean isRunning = Util.findProcess(service.getProcessId());
                        service.setRunning(isRunning);
                    } else {
                        service.setRunning(false);
                    }
                    serviceMap.put(service.getName(), service);
                }
            }
        }
    }

    @Override
    public void addService(Service service) {
        if (serviceMap.containsKey(service.getName())) {
            throw new RuntimeException("已存在同名服务️");
        }
        serviceMap.put(service.getName(), service);
        _saveServiceToFile(service);
        // db.commit();
    }

    @Override
    public void saveService(Service service) {
        if (!serviceMap.containsKey(service.getName())) {
            throw new RuntimeException("服务不存在️");
        }
        Service old = serviceMap.get(service.getName());
        old.setArguments(service.getArguments());
        old.setAuto(service.isAuto());
        old.setMapping(service.isMapping());
        old.setCommands(service.getCommands());
        old.setHome(service.getHome());
        old.setContextPath(service.getContextPath());
        old.setPort(service.getPort());
        old.setTitle(service.getTitle());
        old.setVmOptions(service.getVmOptions());
        old.setMemo(service.getMemo());
        old.setServiceName(service.getServiceName());
        old.setEnabled(service.isEnabled());
        old.setFound(service.isFound());
        old.setMappingPath(service.getMappingPath());
        old.setMappingSetting(service.getMappingSetting());
        old.setArguments(service.getArguments());
        old.setUrl(service.getUrl());

        serviceMap.put(service.getName(), old);
        _saveServiceToFile(service);
        //  db.commit();
    }

    private void _saveServiceToFile(Service service) {
        String content = ONode.stringify(service);
        File dir = new File(servicePath, service.getName());
        if (!dir.exists()) {
            dir.mkdirs();
        }
        StringUtil.write(new File(dir, "service.json"), content);
    }

    @Override
    public String host() {
        return ManagerConfiguration.localIp();
    }

    public void updateService(Service service) {
        addService(service);
    }

    @Override
    public void uploadFile(Service service, Map<String, String> configs, byte[] file) {
        if (!serviceMap.containsKey(service.getName())) {
            throw new RuntimeException("服务不存在");
        }
        service = serviceMap.get(service.getName());
        if (!"jar".equals(service.getType())) {
            throw new RuntimeException("服务类型不正确");
        }
        File dist = getJar(service);
        if (!dist.getParentFile().exists()) {
            dist.getParentFile().mkdirs();
        }
        boolean reStart = false;
        if (processMap.containsKey(service.getName())) {
            reStart = true;
            this.stopService(service);
        }
        try (OutputStream out = new FileOutputStream(dist)) {
            out.write(file);
            out.flush();
            out.close();
            if (reStart) {
                configs.put("port", service.getPort() + "");
                this.startJar(service, configs);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private File getJar(Service service) {
        File dist = new File(servicePath, service.getName() + "/bin.jar");
        return dist;
    }

    @Override
    public boolean startService(Service service, Map<String, String> configs) {
        String serviceName = service.getName();
        if (processMap.containsKey(serviceName)) {
            throw new RuntimeException("服务已经启动");
        }
        Service targetService = serviceMap.get(serviceName);
        if (targetService == null) {
            throw new RuntimeException("服务不存在");
        }
        if(targetService.isRunning()){
            throw new RuntimeException("服务已经启动");
        }
        configs.put("port", targetService.getPort() + "");
        if ("command".equals(targetService.getType())) {
            return startCommand(targetService, configs);
        } else {
            return startJar(targetService, configs);
        }
    }

    private String processConfig(String command, Map<String, String> configs) {
        if (configs == null || configs.size() == 0) {
            return command;
        }
        for (Map.Entry<String, String> e : configs.entrySet()) {
            command = command.replaceAll("\\$\\{\\s+" + e.getKey() + "\\s+\\}", e.getValue());
        }
        return command;
    }

    private boolean startCommand(Service service, Map<String, String> configs) {
        File workDir = new File(service.getWorkDir());
        if (!workDir.exists()) {
            throw new RuntimeException("工作目录错误");
        }
        if (StringUtil.isEmpty(service.getCommands())) {
            throw new RuntimeException("命令不能为空");
        }
        String cmds = processConfig(service.getCommands(), configs);
        List<String> commands = Util.splitPrams(cmds);
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(workDir);
        Utils.pools.submit(() -> {
            try {
                final Process process = pb.start();
                service.setLastStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                //targetService.setProcessId(Util.getPid(process));
                service.setRunning(true);
                processMap.put(service.getName(), process);
                service.setProcessId(Util.getPid(process));
                _saveServiceToFile(service);
                PrintUtil.info("[START] " + String.join(" ", commands));

                Logger logger = getLogger(service);

                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                try {
                    while ((line = bufferedReader.readLine()) != null) {
                        logger.info(line);
                    }
                } catch (IOException io) {

                }
                process.waitFor();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                Process p = processMap.get(service.getName());

                if (!p.isAlive()) {
                    processMap.remove(service.getName());
                    service.setRunning(false);
                    service.setProcessId(null);
                    _saveServiceToFile(service);
                }
            }
        });
        return true;
    }

    private boolean startJar(Service targetService, Map<String, String> configs) {
        File jar = getJar(targetService);
        if (!jar.exists()) {
            throw new RuntimeException("没有找到对应的jar");
        }
        String javaPath = System.getProperty("java.home") + "/bin/java";
        List<String> commands = new ArrayList<>();
        commands.add(javaPath);
        String jvmOpts = targetService.getVmOptions();
        if (!Utils.isEmpty(jvmOpts)) {
            jvmOpts = processConfig(jvmOpts, configs);
            commands.addAll(Util.splitPrams(jvmOpts));
        }
        commands.add("-jar");
        commands.add(jar.getName());
        commands.add("--server.port=" + targetService.getPort());
        String manager=configs.get("manager");
        if(StringUtil.isNotEmpty(manager)){
            commands.add("-easy.manager=" + manager);
        }
        String token=configs.get("token");
        if(StringUtil.isNotEmpty(token)){
            commands.add("-easy.token=" + token);
        }
        String args = targetService.getArguments();
        if (!Utils.isEmpty(args)) {
            args = processConfig(args, configs);
            commands.addAll(Util.splitPrams(args));
        }
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(jar.getParentFile());

       // executorService.submit(() -> {
        Utils.pools.submit(() -> {
            try {
                Process process = pb.start();
                //targetService.setProcessId(Util.getPid(process));
                targetService.setLastStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                targetService.setRunning(true);
                processMap.put(targetService.getName(), process);
                targetService.setProcessId(Util.getPid(process));
                _saveServiceToFile(targetService);
                PrintUtil.info("[START] " + String.join(" ", commands));
                Logger logger = getLogger(targetService);
                Thread t=new Thread(()->{
                    Logger errorLogger = getErrorLogger(targetService);
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                    String line;
                    try {
                        while (targetService.isRunning()&&(line = bufferedReader.readLine()) != null) {
                            errorLogger.error(line);
                            logger.info(line);
                        }
                    } catch (IOException io) {

                    }
                });
                t.start();

                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                try {
                    while ((line = bufferedReader.readLine()) != null) {
                        logger.info(line);
                    }
                } catch (IOException io) {

                }
                process.waitFor();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                Process p = processMap.get(targetService.getName());
                if (!p.isAlive()) {
                    processMap.remove(targetService.getName());
                    targetService.setRunning(false);
                    targetService.setProcessId(null);
                    _saveServiceToFile(targetService);
                }
            }
        });
        return true;
    }

    private Map<String, Logger> loggerMap = new ConcurrentHashMap<>();
    private Map<String, Logger> errorLoggerMap = new ConcurrentHashMap<>();
    /**
     * 获取服务对应的Logger,用于把日志记录到文件
     *
     * @param service
     * @return
     */
    private Logger getLogger(Service service) {
        Logger logger = loggerMap.get(service.getName());
        if (logger != null) {
            return logger;
        }
        File jar = getJar(service);
        logger = LoggerBuilder.build(service.getName(), new File(jar.getParentFile(), "logs/info").getAbsolutePath());
        loggerMap.put(service.getName(), logger);
        return logger;
    }
    /**
     * 获取服务对应的Logger,用于把错误日志记录到文件
     *
     * @param service
     * @return
     */
    private Logger getErrorLogger(Service service) {
        Logger logger = errorLoggerMap.get(service.getName());
        if (logger != null) {
            return logger;
        }
        File jar = getJar(service);
        logger = LoggerBuilder.buildForError(service.getName(), new File(jar.getParentFile(), "logs/error").getAbsolutePath());
        errorLoggerMap.put(service.getName(), logger);
        return logger;
    }
    @Override
    public boolean stopService(Service service) {
        Process process = processMap.get(service.getName());
        if (process != null) {
            process.destroyForcibly();
            loggerMap.get(service.getName()).info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " - Stopped from Manager.");
        }else{
            Service sv=serviceMap.get(service.getName());
            if(sv.isRunning()&&sv.getProcessId()!=null){
                Util.stopProcess(sv.getProcessId());
                sv.setRunning(false);
            }
        }
        return true;
    }

    @Override
    public void deleteService(Service service) {

        this.stopService(service);
        serviceMap.remove(service.getName());

        File dir = new File(servicePath, service.getName());
        Util.deleteDir(dir);
        // db.commit();
    }

    @Override
    public Collection<Service> listService() {
        return serviceMap.values();
    }

    @Override
    public LogPart getLog(String service, long pos) {
        String basePath = Solon.cfg().get("local.path", "./manager/");

        String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File logFile = new File(basePath, "services/" + service + "/logs/info-" + date + ".0.log");
        if (!logFile.exists()) {
            return new LogPart();
        }
        try (RandomAccessFile raf = new RandomAccessFile(logFile, "r")) {
            long length = raf.length();
            if (pos == 0) {
                //如果pos==0，获取最后10行
                int rows = 50;
                byte[] c = new byte[1];
                // 在获取到指定行数和读完文档之前,从文档末尾向前移动指针,遍历文档每一个字节
                for (long pointer = raf.length(), lineSeparatorNum = 0; pointer >= 0 && lineSeparatorNum < rows; ) {
                    // 移动指针
                    raf.seek(pointer--);
                    // 读取数据
                    int readLength = raf.read(c);
                    if (readLength != -1 && c[0] == 10) {
                        lineSeparatorNum++;
                    }
                    // 扫描完依然没有找到足够的行数,将指针归0
                    if (pointer == -1 && lineSeparatorNum < rows) {
                        raf.seek(0);
                    }
                }
            } else if (pos < length) {
                raf.seek(pos);
            } else {
                return new LogPart();
            }

            byte[] tempbytes = new byte[(int) (raf.length() - raf.getFilePointer())];
            raf.readFully(tempbytes);
            String content = new String(tempbytes, "UTF-8");
            return new LogPart(length, content);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new LogPart();
    }


    private void checkPath(String path) {
        if (path == null || path.indexOf("..") > -1) {
            throw new RuntimeException("路径错误");
        }
    }

    /**
     * ========================= file api =========================
     **/
    @Override
    public List<FileInfo> fileList(String path) {
        if (StringUtil.isEmpty(path)) {
            path = "/";
        }
        checkPath(path);
        File file = new File(ManagerConfiguration.localPath(), path);
        if (!file.exists()) {
            return new ArrayList<>();
        }
        List result = new ArrayList();
        for (File f : file.listFiles()) {
            String name = f.getName();
            String type = "unknown";
            if (f.isDirectory()) {
                type = "dir";
            } else if (name.indexOf(".") > -1) {
                type = name.substring(name.lastIndexOf(".") + 1).toLowerCase();
            }

            FileInfo fi = new FileInfo(name, f.length(), f.lastModified(), type);
            result.add(fi);
        }
        return result;
    }

    /**
     * 返回文件的文本内容
     *
     * @param path
     * @return
     */
    @Override
    public String loadFile(String path) {
        checkPath(path);
        File f = new File(ManagerConfiguration.localPath(), path);
        if (f.exists()) {
            return StringUtil.readFrom(f);
        }
        return null;
    }

    /**
     * 保存文本文件
     *
     * @param path
     * @param file
     */
    @Override
    public void saveFile(String path, String file) {
        checkPath(path);
        File f = new File(ManagerConfiguration.localPath(), path);
        if (f.exists() && f.isDirectory()) {
            throw new RuntimeException("文件路径错误");
        }
        StringUtil.write(f, file);
    }

    /**
     * 导出压缩文件
     *
     * @param path
     * @return
     */
    @Override
    public FileData exportFile(String path) {
        checkPath(path);
        File f = new File(ManagerConfiguration.localPath(), path);
        if (!f.exists()) {
            throw new RuntimeException("文件不存在");
        }
        if (f.isDirectory()) {//压缩导出
            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                Util.toZip(f.getAbsolutePath(), out, true);
                FileData fout = new FileData();
                fout.setName(f.getName() + ".zip");
                fout.setData(out.toByteArray());
                return fout;
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            try (InputStream in = new FileInputStream(f)) {
                byte[] data = Utils.transferToBytes(in);
                FileData fout = new FileData();
                fout.setName(f.getName());
                fout.setData(data);
                return fout;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public void uploadServerFile(String path, FileData fd) {
        checkPath(path);
        File dir = new File(ManagerConfiguration.localPath(), path);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File dist = new File(dir, fd.getName());
        try (OutputStream fout = new FileOutputStream(dist)) {
            fout.write(fd.getData());
            fout.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean unZipFile(String path) {
        checkPath(path);
        File file = new File(ManagerConfiguration.localPath(), path);
        if (file.exists()) {
            if (file.getName().endsWith(".zip")) {
                try {
                    Util.unZipFiles(file, file.getParent());
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    @Override
    public boolean removeFile(String path) {
        checkPath(path);
        File file = new File(ManagerConfiguration.localPath(), path);
        if (!file.exists()) {
            return false;
        }
        if (file.isFile()) {
            return file.delete();
        }
        return Util.deleteDir(file);
    }

    private static final Detector[] allDetectors = new Detector[]{
            new CpuDetector(),
            new OsDetector(),
            new MemoryDetector(),
            new FileDetector()
    };

    @Override
    public Map<String, Object> status() {
        Map<String, Object> info = new HashMap<>();
        for (Detector detector : allDetectors) {
            info.put(detector.getName(), detector.getInfo());
        }
        return info;
    }

    @Override
    public Map<String, Object> serviceStatus(Service service) {
        Process process = processMap.get(service.getName());
        Map<String, Object> info = new HashMap<>();
        if (process != null) {
            long pid = Util.getPid(process);
            ProcessDetector processDetector = new ProcessDetector(pid);
            info.put("process", processDetector.getInfo());
        } else {
            Map<String, Object> processInfo = new HashMap<>();
            processInfo.put("CPU", 0);
            processInfo.put("MEM", 0);
            processInfo.put("RSS", 0);//内存使用量

            OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
            long totalMemory = osmxb.getTotalPhysicalMemorySize();
            processInfo.put("TotalMemory", totalMemory);

            info.put("process", processInfo);
        }
        if ("jar".equals(service.getType())) {
            FileDetector fileDetector = new FileDetector(getJar(service));
            info.put("file", fileDetector.getInfo());
        } else {
            FileDetector fileDetector = new FileDetector(new File(service.getWorkDir()));
            info.put("file", fileDetector.getInfo());
        }
        return info;
    }

    @Override
    public void newFile(String path, String file) {
        checkPath(path);
        File f = new File(ManagerConfiguration.localPath(), path);
        if (!f.exists() && !f.isDirectory()) {
            throw new RuntimeException("文件路径错误");
        }
        File distFile=new File(f,file);
        if(distFile.exists()){
            throw new RuntimeException("文件已存在");
        }
        try {
            distFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void newDir(String path, String file) {
        checkPath(path);
        File f = new File(ManagerConfiguration.localPath(), path);
        if (!f.exists() && !f.isDirectory()) {
            throw new RuntimeException("文件路径错误");
        }
        File distFile=new File(f,file);
        if(distFile.exists()){
            throw new RuntimeException("文件已存在");
        }
        distFile.mkdirs();
    }

    @Override
    public boolean reName(String path, String newName) {
        checkPath(path);
        File file = new File(ManagerConfiguration.localPath(), path);
        if (!file.exists()) {
            return false;
        }
        if(newName.indexOf(File.separator)>-1||newName.indexOf("..")>-1){
            return false;
        }
        return file.renameTo(new File(file.getParentFile(),newName));
    }
}
